package edu.ucdavis.gwt.gis.client.query;

import java.util.LinkedList;


import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.core.client.JsArray;
import com.google.gwt.core.client.JsArrayString;
import com.google.gwt.dom.client.Style.Unit;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.logical.shared.AttachEvent;
import com.google.gwt.event.logical.shared.ResizeEvent;
import com.google.gwt.event.logical.shared.ResizeHandler;
import com.google.gwt.event.logical.shared.AttachEvent.Handler;
import com.google.gwt.user.client.Timer;
import com.google.gwt.user.client.Window;
import com.google.gwt.user.client.ui.PopupPanel;
import com.google.gwt.user.client.ui.SimplePanel;
import com.google.gwt.user.client.ui.VerticalPanel;
import com.google.gwt.user.client.ui.Widget;

import edu.ucdavis.cstars.client.Error;
import edu.ucdavis.cstars.client.Graphic;
import edu.ucdavis.cstars.client.MapWidget;
import edu.ucdavis.cstars.client.Graphic.Attributes;
import edu.ucdavis.cstars.client.callback.QueryTaskCallback;
import edu.ucdavis.cstars.client.dojo.Color;
import edu.ucdavis.cstars.client.event.ClickHandler;
import edu.ucdavis.cstars.client.event.MouseEvent;
import edu.ucdavis.cstars.client.event.MouseMoveHandler;
import edu.ucdavis.cstars.client.geometry.Extent;
import edu.ucdavis.cstars.client.geometry.Geometry;
import edu.ucdavis.cstars.client.geometry.Multipoint;
import edu.ucdavis.cstars.client.geometry.Point;
import edu.ucdavis.cstars.client.geometry.Polygon;
import edu.ucdavis.cstars.client.geometry.Polyline;
import edu.ucdavis.cstars.client.geometry.Geometry.GeometryType;
import edu.ucdavis.cstars.client.layers.LayerInfo;
import edu.ucdavis.cstars.client.symbol.SimpleFillSymbol;
import edu.ucdavis.cstars.client.symbol.SimpleLineSymbol;
import edu.ucdavis.cstars.client.symbol.Symbol;
import edu.ucdavis.cstars.client.tasks.FeatureSet;
import edu.ucdavis.cstars.client.tasks.Query;
import edu.ucdavis.cstars.client.tasks.QueryTask;
import edu.ucdavis.gwt.gis.client.DataManager;
import edu.ucdavis.gwt.gis.client.auth.DomainToken;
import edu.ucdavis.gwt.gis.client.extras.CloseButton;
import edu.ucdavis.gwt.gis.client.layers.DataLayer;
import edu.ucdavis.gwt.gis.client.layers.MapServerDataLayer;
import edu.ucdavis.gwt.gis.client.query.StatusPanel.NavHandler;
import edu.ucdavis.gwt.gis.client.resources.GadgetResources;

public class QueryTool extends PopupPanel {
	
	private VerticalPanel mainPanel = new VerticalPanel();
	
	private SimplePanel content = new SimplePanel();
	private StatusPanel statusPanel = new StatusPanel();
	
	private Widget innerContent = null;
	
	private Graphic currentGraphic = null;
	private Point currentPoint;
	private DataLayer dl = null;
	private LinkedList<LayerInfo> sublayerInfos = null;
	private int queriesReturned = 0;
	private boolean showing = false;
	private LinkedList<SubLayerGraphic> graphics = new LinkedList<SubLayerGraphic>();
	private LinkedList<QueryTaskWrapper> pendingRequests = new LinkedList<QueryTaskWrapper>();
	
	private BboxQueryGraphic bboxQuery;
	
	private static int WIDTH = 500;
	private static int HEIGHT = 410;
	
	int startLeft = 0;
	int startTop = 0;
	int startWidth = 0;
	int startHeight = 0;
	
	private RenderPopupHandler renderHandler = null;
	
	private Symbol symbol = SimpleFillSymbol.create(
			SimpleFillSymbol.StyleType.STYLE_SOLID, 
			SimpleLineSymbol.create(
					SimpleLineSymbol.StyleType.STYLE_SOLID,
					Color.create(34, 120, 218, 1),
					1), 
			Color.create(34, 120, 218, .6));
	
	private MapWidget map;

	private PointSelectPanel pointAnimation = new PointSelectPanel();
	private NoResultsPopup noResultsPopup = new NoResultsPopup(pointAnimation);
	private QueryToolSettings settings;
	
	public void setDefaultQueryLayer(MapServerDataLayer msdl) {
		this.settings.setDataLayer(msdl);
	}
	
	public QueryTool(MapWidget map) {
		this.map = map;
		settings = QueryToolSettings.INSTANCE;
		
		pointAnimation.setMap(map);
		noResultsPopup.setMap(map);
		bboxQuery = new BboxQueryGraphic(map);
		
		mainPanel.setWidth("100%");
		
		CloseButton close = new CloseButton();
		close.getElement().getStyle().setMarginTop(3, Unit.PX);
		close.addClickHandler(new com.google.gwt.event.dom.client.ClickHandler(){
			@Override
			public void onClick(ClickEvent event) {
				hide();
			}
		});
		mainPanel.add(close);
		mainPanel.setCellHorizontalAlignment(close, VerticalPanel.ALIGN_RIGHT);
		content.setHeight("358px");
		mainPanel.add(content);
		statusPanel.setNavHandler(handler);
		mainPanel.add(statusPanel);
		setWidget(mainPanel);
		
		setStyleName("DownloadPanel-detached");
		setAutoHideEnabled(true);
		
		map.addClickHandler(new ClickHandler(){
			@Override
			public void onClick(MouseEvent event) {
				if( settings.isActive() && settings.isPointQuery() ) {
					pointAnimation.show(event.getMapPoint());
					queryPoint(event.getMapPoint());
				} else if ( settings.isActive() && settings.isBboxQuery() ) {
					if( bboxQuery.isActive() ) {
						queryBbox(bboxQuery.end(event.getMapPoint()));
					} else {
						bboxQuery.start(event.getMapPoint());
					}
				}
			}
		});
		
		Window.addResizeHandler(new ResizeHandler(){
			@Override
			public void onResize(ResizeEvent event) {
				if( isAttached() ) center();
			}
		});
		
		addAttachHandler(new Handler(){
			@Override
			public void onAttachOrDetach(AttachEvent event) {
				if( !event.isAttached() ) {
					removeGraphic();
				}
			}
		});
	}
	
	private void cancelPending() {
		for( QueryTaskWrapper qtw: pendingRequests ) {
			qtw.setCancelled();
		}
		pendingRequests.clear();
	}
	
	private void queryPoint(Point p) {
		currentPoint = p;
		double scale = Extent.getScale(map);
		double screenScale = scale*0.000254*4;		
		Extent queryBox = Extent.create(p.getX()-screenScale, p.getY()-screenScale, screenScale+p.getX(), screenScale+p.getY(), map.getSpatialReference());
		query(queryBox);
	}
	
	private void queryBbox(Extent ext) {
		currentPoint = ext.getCenter();
		query(ext);
	}
	
	private void query(Geometry geom) {
		cancelPending();
		statusPanel.setVisible(false);
		
		
		dl = settings.getQueryLayer();
		if( dl == null ) return;
		
		sublayerInfos = settings.getSelectedLayerInfos();
		
		queriesReturned = 0;
		showing = false;
		graphics.clear();
		removeGraphic();
		int total = 0;
		statusPanel.reset();
		for( LayerInfo info: sublayerInfos ) {

			double scale = Extent.getScale(map);
			double minScale = info.getMinScale();
			double maxScale = info.getMaxScale();
			if( minScale < 0 ) minScale = 0;
			if( maxScale < 0 ) maxScale = 0;			
			
			if( ((minScale >= scale) && (scale >= maxScale)) || (minScale == 0 && maxScale == 0) ) {
							
				String qurl = dl.getUrl()+"/"+info.getId();
				DomainToken dt = DataManager.INSTANCE.getDomainAccess().getDomainToken(qurl);
				if( dt != null ) qurl += "?token="+dt.getToken();
 				
				QueryTask queryTask = QueryTask.create(qurl);
				Query q = Query.create();
	
				q.setGeometry(geom);
				
				if( settings.highlight() ) {
					q.setReturnGeometry(true);
					q.setOutSpatialReference(map.getSpatialReference());
				}
				
				q.setOutFields(new String[] {"*"});
				QueryTaskWrapper wrapper = new QueryTaskWrapper(info);
				queryTask.execute(q, wrapper.getCallback());
				pendingRequests.add(wrapper);
				total++;
			}
		}
		statusPanel.setTotal(total);
	}

	private void removeGraphic() {
		if( currentGraphic != null ) {
			map.getGraphics().remove(currentGraphic);
			currentGraphic = null;
		}
	}
	
	public void showQuery(SubLayerGraphic g) {
		content.clear();
		setStyleName("DownloadPanel-detached");
		
		removeGraphic();
		currentGraphic = g.getGraphic();
		
		if( currentGraphic.getGeometry() != null ) {
			Extent extent = null;
			
			if( currentGraphic.getGeometry().getType() == GeometryType.POLYGON ) {
				extent = ((Polygon) currentGraphic.getGeometry()).getExtent();
			} else if( currentGraphic.getGeometry().getType() == GeometryType.MULTIPOINT ) {
				extent = ((Multipoint) currentGraphic.getGeometry()).getExtent();
			} else if( currentGraphic.getGeometry().getType() == GeometryType.POINT ) {
				Point p = (Point) currentGraphic.getGeometry();
				extent = Extent.create(p.getX(), p.getY(), p.getX(), p.getY(), p.getSpatialReference());
			} else if( currentGraphic.getGeometry().getType() == GeometryType.POLYLINE ) {
				extent = ((Polyline) currentGraphic.getGeometry()).getExtent();
			}
			
			Extent e = (Extent) Extent.toScreenGeometry(map.getExtent(), map.getWidth(), map.getHeight(), extent);
			startLeft = (int) Math.floor(e.getXMin());
			startTop = (int) Math.floor(e.getYMax());
			startWidth = (int) Math.floor(e.getXMax()) - startLeft;
			startHeight = (int) Math.floor(e.getYMin()) - startTop;
			
			currentGraphic.setSymbol(symbol);
			map.getGraphics().add(currentGraphic);
		} else {
			Point p = (Point) Geometry.toScreenGeometry(map.getExtent(), map.getWidth(), map.getHeight(), currentPoint);
			startLeft = (int) Math.floor(p.getX()) - 16;
			startTop = (int) Math.floor(p.getY()) - 16;
			startWidth = 36;
			startHeight = 36;
		}
		
		show();
		
		
		showTimer.schedule(50);
		
		getElement().getStyle().setHeight(startHeight, Unit.PX);
		getElement().getStyle().setWidth(startWidth, Unit.PX);
		getElement().getStyle().setLeft(startLeft, Unit.PX);
		getElement().getStyle().setTop(startTop, Unit.PX);
		
		if( renderHandler != null ) {
			innerContent = renderHandler.createContent(dl.getLabel(), g.getSubLayerInfo(), currentGraphic);
		} else {
			innerContent = createContent(dl.getLabel(), g.getSubLayerInfo());
		}
		innerContent.setStyleName("DownloadPopupContent");
	}
	
	public void setRenderHandler(RenderPopupHandler handler) {
		renderHandler = handler;
	}
	
	private QueryPopupContent createContent(String title, LayerInfo info) {
		QueryPopupContent content = new QueryPopupContent();
		
		content.setTitle(title);
		content.setName(info.getName());
		
		Attributes attrs = currentGraphic.getAttributes();
		JsArrayString keys = attrs.getKeys();
		for( int i = 0; i < keys.length(); i++ ) {
			content.addAttribute(keys.get(i), attrs.getStringForced(keys.get(i)));
		}
		
		return content;
	}
	
	private Timer showTimer = new Timer() {
		@Override
		public void run() {
			setStyleName("DownloadPanel-attached");
			
			getElement().getStyle().setWidth(WIDTH, Unit.PX);
			getElement().getStyle().setHeight(HEIGHT, Unit.PX);
			
			int left = (int) (Math.floor(Window.getClientWidth() / (double) 2) - Math.floor(WIDTH / (double) 2));
			int top = (int) (Math.floor(Window.getClientHeight() / (double) 2) - Math.floor(HEIGHT / (double) 2));
			
			getElement().getStyle().setLeft(left, Unit.PX);
			getElement().getStyle().setTop(top, Unit.PX);
			showContentTimer.schedule(500);
		}
	};
	
	private Timer showContentTimer = new Timer() {
		@Override
		public void run() {
			content.add(innerContent);
			showInnerContentTimer.schedule(50);
			statusPanel.setVisible(true);
		}
	};
	
	private Timer showInnerContentTimer = new Timer() {
		@Override
		public void run() {
			innerContent.setStyleName("DownloadPopupContent-show");
		}
	};
	
	private NavHandler handler = new NavHandler() {
		@Override
		public void onNavigate(int featureNum) {
			if( graphics.size() > featureNum ) {
				SubLayerGraphic g = graphics.get(featureNum);
				
				removeGraphic();
				currentGraphic = g.getGraphic();
				
				
				if( renderHandler != null ) {
					innerContent = renderHandler.createContent(dl.getLabel(), g.getSubLayerInfo(), currentGraphic);
				} else {
					innerContent = createContent(dl.getLabel(), g.getSubLayerInfo());
				}
				innerContent.setStyleName("DownloadPopupContent");
				
				content.clear();
				showContentTimer.schedule(50);
				
				if( currentGraphic.getGeometry() != null ) {
					currentGraphic.setSymbol(symbol);
					map.getGraphics().add(currentGraphic);
				}
			}
		}
	};
	
	private class QueryTaskWrapper {
		private LayerInfo info = null;
		private boolean canceled = false;
		public QueryTaskWrapper(LayerInfo info) {
			this.info = info;
		}
		public QueryTaskCallback getCallback() {
			return callback;
		}
		private QueryTaskCallback callback = new QueryTaskCallback() {
			@Override
			public void onComplete(FeatureSet featureSet) {
				if( canceled ) return;
				JsArray<Graphic> features = featureSet.getFeatures();
				for( int i = 0; i < features.length(); i++ ) {
					graphics.add(new SubLayerGraphic(features.get(i), info));	
				}
				statusPanel.setFeaturesReturned(graphics.size());
				
				if( !showing && graphics.size() > 0 ) {
					showQuery(graphics.get(0));
					showing = true;
				}
				queriesReturned++;
				statusPanel.setQueriesReturned(queriesReturned);
				
				if( queriesReturned == statusPanel.getTotal() ) {
					if( graphics.size() == 0 ) {
						// this hides the point animation
						noResultsPopup.show(currentPoint);
					} else {
						pointAnimation.hide();
					}
				}
			}
			@Override
			public void onError(Error error) {
				if( canceled ) return;
				queriesReturned++;
				// fail silently
				statusPanel.setQueriesReturned(queriesReturned);
				
				if( queriesReturned == statusPanel.getTotal() ) {
					if( graphics.size() == 0 ) {
						// this hides the point animation
						noResultsPopup.show(currentPoint);
					} else {
						pointAnimation.hide();
					}
				}
			}
		};
		public void setCancelled() {
			canceled = true;
		}
	}
	
	private class SubLayerGraphic {
		private Graphic g;
		private LayerInfo info;
		public SubLayerGraphic(Graphic g, LayerInfo info) {
			this.g = g;
			this.info = info;
		}
		public Graphic getGraphic() {
			return g;
		}
		public LayerInfo getSubLayerInfo() {
			return info;
		}
	}
	
	
	private class BboxQueryGraphic {
		
		private Graphic graphic = null;
		private Symbol symbol = null;
		private Point firstPoint = null;
		private Polygon poly = null;
		private boolean active = false;
		
		public BboxQueryGraphic(MapWidget map) {
			symbol = SimpleFillSymbol.create(
					SimpleFillSymbol.StyleType.STYLE_SOLID, 
					SimpleLineSymbol.create(
							SimpleLineSymbol.StyleType.STYLE_SOLID,
							Color.create(255, 0, 0, .9),
							2), 
					Color.create(0, 0, 0, .4));
			
			map.addMouseMoveHandler(new MouseMoveHandler() {
				@Override
				public void onMouseMove(MouseEvent event) {
					if( active ) update(event.getMapPoint());
				}
			});
		}
		
		public void start(Point p) {
			firstPoint = p;
			active = true;
			update(p);
		}
		
		private void update(Point p) {
			JavaScriptObject pJso = null;
			if( p.getX() < firstPoint.getX() ) {
				if( p.getY() < firstPoint.getY() ) {
					pJso = createPolyJso(p.getX(), p.getY(), firstPoint.getX(), firstPoint.getY(), p.getSpatialReference());
				} else {
					pJso = createPolyJso(p.getX(), firstPoint.getY(), firstPoint.getX(), p.getY(), p.getSpatialReference());
				}
			} else {
				if( p.getY() < firstPoint.getY() ) {
					pJso = createPolyJso(firstPoint.getX(), p.getY(), p.getX(), firstPoint.getY(), p.getSpatialReference());
				} else {
					pJso = createPolyJso(firstPoint.getX(), firstPoint.getY(), p.getX(), p.getY(), p.getSpatialReference());
				}
			}
			poly = Polygon.create(pJso);
			if( graphic != null ) map.getGraphics().remove(graphic);
			graphic = Graphic.create(poly, symbol);
			map.getGraphics().add(graphic);
		}
		
		public boolean isActive() {
			return active;
		}
		
		public Extent end(Point p) {
			pointAnimation.show(((Polygon) graphic.getGeometry()).getExtent().getCenter());
			active = false;
			map.getGraphics().remove(graphic);
			graphic = null;
			
			Extent ext = null;
			if( p.getX() < firstPoint.getX() ) {
				if( p.getY() < firstPoint.getY() ) {
					ext = Extent.create(p.getX(), p.getY(), firstPoint.getX(), firstPoint.getY(), p.getSpatialReference());
				} else {
					ext = Extent.create(p.getX(), firstPoint.getY(), firstPoint.getX(), p.getY(), p.getSpatialReference());
				}
			} else {
				if( p.getY() < firstPoint.getY() ) {
					ext = Extent.create(firstPoint.getX(), p.getY(), p.getX(), firstPoint.getY(), p.getSpatialReference());
				} else {
					ext = Extent.create(firstPoint.getX(), firstPoint.getY(), p.getX(), p.getY(), p.getSpatialReference());
				}
			}
			
			return ext;
		}

		private native JavaScriptObject createPolyJso(double xmin, double ymin, double xmax, double ymax, JavaScriptObject sr) /*-{
			return {
				"rings":[
					[	[xmin, ymax],
						[xmax, ymax],
						[xmax, ymin],
						[xmin, ymin],
						[xmin, ymax],
					]
				],"spatialReference":sr
			};
		}-*/;
		
	}
	

}
